package Socket.Coelho;

import Common.Coelho;
import Common.EstadoCoelho;
import Server.Prado;
import java.util.Random;
import java.net.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import Socket.*;


public class CoelhoSocket extends Coelho implements Runnable{

    private Prado field;
    private final String host;
    private final int port;
    private ObjectOutputStream objout;
    private ObjectInputStream objin;
    private Socket com;
    private Message saída;
    private Response entrada = null;

    public CoelhoSocket (Prado temp, int id, int Ef, int Es, int Ej, int Et, int line, int column, int nl, int nc, String Host, int porto, int server_pot) {
        super(id, Es + Ef/2, Es, Ej, Et, line, column, nl, nc);
        this.field = temp;
        this.host = Host;
        this.port = porto;
        this.com = new Socket();
        try {
            com.connect(new InetSocketAddress (host, port));
        }
        catch (UnknownHostException e) {
            System.out.println ("Coelho - o nome do sistema computacional onde reside o servidor é desconhecido: " + host);
            e.printStackTrace ();
            System.exit (1);
        }
        catch (NoRouteToHostException e) {
            System.out.println ("Coelho - o sistema computacional onde reside o servidor é inatingível: " + host);
            e.printStackTrace ();
            System.exit (1);
        }
        catch (ConnectException e) {
            System.out.println ("Coelho - o servidor não está activo em: " + host + "." + port);
            System.out.println (e.getMessage ());
            e.printStackTrace ();
            System.exit (1);
        }
        catch (SocketTimeoutException e) {
        System.out.println ("Coelho - ocorreu um time out no estabelecimento da ligação a: " + host + "." + port);
        }
        catch (SocketException e) {
            System.out.println  ("Coelho - fila de espera cheia no estabelecimento da ligação a: " + host + "." + port);
        }
        catch (IOException e) {
            System.out.println ("Coelho - ocorreu um erro indeterminado no estabelecimento da ligação: " + host + "." + port);
            e.printStackTrace ();
            System.exit (1);
        }
    }

    private void jump () {
        int dest = this.chooseJumpDest();

        switch(dest) {
            case 0:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX()-1, this.getY()-1)) {
                    this.setX(this.getX()-1);
                    this.setY(this.getY()-1);   
                }
                break;
            case 1:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX(), this.getY()-1))
                    this.setY(this.getY()-1);
                break;                
            case 2:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX()+1, this.getY()-1)) {
                    this.setX(this.getX()+1);
                    this.setY(this.getY()-1);
                }
                break;
            case 3:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX()+1, this.getY()))
                    this.setX(this.getX()+1);
                break;
            case 4:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX()+1, this.getY()+1)) {
                    this.setX(this.getX()+1);
                    this.setY(this.getY()+1);
                }
                break;
            case 5:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX(), this.getY()+1))
                    this.setY(this.getY()+1);
                break;
            case 6:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX()-1, this.getY()+1)) {
                    this.setX(this.getX()-1);
                    this.setY(this.getY()+1);
                }
                break;
            case 7:
                if(testJump(this.getId(), this.getX(), this.getY(), this.getX()-1, this.getY()))
                    this.setX(this.getX()-1);
                break;
        }
        this.decEnergy(this.getE_j());
    }

    private void die () {
        this.setState(EstadoCoelho.Dead);
        saída = new Message(TypeFunction.informCoelho, this.getId(), 0, 0, this.getX(), this.getY(), 0, 0, EstadoCoelho.Dead, null);
                try {
                    objout.writeObject((Object)saída);
                }
                catch(IOException ex) {
                    System.out.println("Coelho "+this.getId()+" - Falha --> Write Object --> die ()");
                    System.exit(-1);
                }
        super.log.Log("Coelho " + this.getId() + " morreu na posicao X-Y: " + this.getX() + "-" + this.getY());
    }

    private void get_hungry () {
        this.setState(EstadoCoelho.Searching);
        saída = new Message(TypeFunction.informCoelho, this.getId(), 0, 0, this.getX(), this.getY(), 0, 0, EstadoCoelho.Searching, null);
                try {
                    objout.writeObject((Object)saída);
                }
                catch(IOException ex) {
                    System.out.println("Coelho "+this.getId()+" - Falha --> Write Object --> get_hungry ()");
                    System.exit(-1);
                }
    }

    private void eat_the_carrot () {
        saída = new Message(TypeFunction.carrotEnergy, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
                try {
                    objout.writeObject((Object)saída);
                    entrada = (Response)objin.readObject();
                }
                catch(IOException ex) {
                    System.out.println("Coelho "+this.getId()+" - Falha --> Write Object --> eat_the_carrot ()");
                    System.exit(-1);
                }
                catch(ClassNotFoundException ex) {
                    System.exit(1);
                }
        this.setEnergia(this.getEnergia() + entrada.getiValor() / 2);
    }

    private void get_satiated () {
        this.setState(EstadoCoelho.Wandering);
        saída = new Message(TypeFunction.informCoelho, this.getId(), 0, 0, this.getX(), this.getY(), 0, 0, EstadoCoelho.Wandering, null);
                try {
                    objout.writeObject((Object)saída);
                }
                catch(IOException ex) {
                    System.out.println("Coelho "+this.getId()+" - Falha --> Write Object --> get_satiated ()");
                    System.exit(-1);
                }
    }

    public void run () {
        Random gerador = new Random(this.getId() + 10);

        try {
            objout = new ObjectOutputStream(com.getOutputStream());
            objin = new ObjectInputStream(com.getInputStream());
        }
        catch(IOException ex) {
            System.out.println("Foi impossível criar streams do coelho " + this.getId() + ".");
            System.out.println(ex.getMessage());
            System.exit(1);
        }

        //State Machine
        while(this.getState() != EstadoCoelho.Dead)
        {
            switch(this.getState())
            {
                case Wandering:
                    if(this.getEnergia() > this.getE_s())
                        this.jump();
                    else
                        this.get_hungry();
                    break;

                case Searching:
                    if(this.getEnergia() <= 0)
                        this.die();
                    else {
                        if(this.getEnergia() > this.getE_s())
                            this.get_satiated();
                        else {
                            saída = new Message(TypeFunction.hasCarrot, 0, 0, 0, this.getX(), this.getY(), 0, 0, null, null);
                            try {
                                objout.writeObject((Object)saída);
                                entrada = (Response)objin.readObject();
                            }
                            catch(IOException ex) {
                                System.out.println("Coelho "+this.getId()+" - Falha --> !=EstadoCoelho.Dead --> case Searching");
                                System.exit(-1);
                            }
                            catch(ClassNotFoundException ex) {
                                System.exit(1);
                            }

                            if(entrada.getbValor())
                                this.eat_the_carrot();
                            else
                                this.jump();
                        }
                    }
            }
            saída = new Message(TypeFunction.getEndFlag, 0, 0, 0, 0, 0, 0, 0, null, null);
            try {
                objout.writeObject((Object)saída);
                entrada = (Response)objin.readObject();
            }
            catch(IOException ex) {
                System.out.println("Coelho "+this.getId()+" - saída = new Message(TypeFunction.getEndFlag, 0, 0, 0, 0, 0, 0, 0, null, null);");
                System.exit(-1);
            }
            catch(ClassNotFoundException ex) {
                System.exit(1);
            }

            if(entrada.getbValor()) {
                this.setState(EstadoCoelho.Dead);
                saída = new Message(TypeFunction.informCoelho, this.getId(), 0, 0, this.getX(), this.getY(), 0, 0, EstadoCoelho.Dead, null);
                try {
                    objout.writeObject((Object)saída);
                }
                catch(IOException ex) {
                    System.out.println("Coelho "+this.getId()+" - saída = new Message(TypeFunction.informCoelho, this.getId(), 0, 0, this.getX(), this.getY(), 0, 0, EstadoCoelho.Dead, null);");
                    System.exit(-1);
                }
            }
            try {
                Thread.sleep(gerador.nextInt(201) + 900);
                this.decEnergy(this.getE_t());
            }
            catch(InterruptedException ex) {
                System.out.println(ex.getMessage());
                return;
            }
        }

    }

    //Métodos Auxiliares --> Privados

    private boolean testJump(int id_rabbit, int oldX, int oldY, int newX, int newY) {
        saída = new Message(TypeFunction.canJump, id_rabbit, 0, 0, oldX, oldY, newX, newY, null, null);
        try {
            objout.writeObject((Object)saída);
            entrada = (Response)objin.readObject();
        }
        catch(IOException ex) {
            System.out.println("Coelho "+this.getId()+" - saída = new Message(TypeFunction.canJump, id_rabbit, 0, 0, oldX, oldY, newX, newY, null, null);");
            System.exit(-1);
        }
        catch(ClassNotFoundException ex) {
            System.exit(1);
        }

        return entrada.getbValor();
    }

    /**
     * Função que efectua decrementa a energia do coelho conforme o valor especificado.
     * @param decVal -> valor a decrementar
     */
    private void decEnergy(int decVal) {
        this.setEnergia(this.getEnergia() - decVal);
    }

    /**
     * Função que escolhe, aleatoriamente, a direcção de salto do coelho.
     * 0 - NW
     * 1 - N
     * 2 - NE
     * 3 - E            0   1   2
     * 4 - SE           7       3
     * 5 - S            6   5   4
     * 6 - SW
     * 7 - W
     * @return direcção do salto
     */
    private int chooseJumpDest() {
        Random gen = new Random();
        int direction;

        if(this.getX() == 0)
        {
            if(this.getY() == 0)
                direction = gen.nextInt(3) + 3;
            else
            {
                if(this.getY() == (this.getNL() - 1))
                    direction = gen.nextInt(3) + 1;
                else
                    direction = gen.nextInt(5) + 1;
            }
        }
        else
        {
            if(this.getX() == (this.getNC() - 1))
            {
                if(this.getY() == 0)
                    direction = gen.nextInt(3) + 5;
                else
                {
                    if(this.getY() == (this.getNL() - 1))
                    {
                        direction = gen.nextInt(3);
                        if(direction == 2)
                            direction = 7;
                    }
                    else
                    {
                        direction = gen.nextInt(5);
                        if(direction > 1)
                            direction += 3;
                    }
                }
            }
            else
            {
                if(this.getY() == 0)
                    direction = gen.nextInt(5) + 3;
                else
                {
                    if(this.getY() == this.getNL() - 1)
                    {
                        direction = gen.nextInt(5);
                        if(direction == 4)
                            direction = 7;
                    }
                    else
                        direction = gen.nextInt(8);
                }
            }
        }
        return direction;
    }

}

